//
//  BindOperator.swift
//  CSBind
//
//  Created by 万孟超 on 2023/12/22.
//

import UIKit

class CSBindOperator<BinderObject: NSObject, HijackeeObject: NSObject, ValueType>: NSObject, CSBindOperatorRelease {
    
    /// 添加属性观察器的实例
    private var observation: NSKeyValueObservation?
    /// 防止同步修改
    private var isSyncing: Bool = false
    
    /// 绑定者实例
    private weak var binderObj: BinderObject?
    /// 绑定者绑定的属性键值
    private var binderKeyPath: ReferenceWritableKeyPath<BinderObject, ValueType>?
    /// 绑定者绑定的属性键值
    private var binderKeyPathString: String?
    
    /// 被绑定者
    private weak var hijackeeObj: HijackeeObject?
    /// 绑定者绑定的属性键值
    private var hijackeeObjKeyPath: ReferenceWritableKeyPath<HijackeeObject, ValueType>?
    /// 被绑定的键值
    private var hijackeeObjKey: String?
    
    /// 当前状态
    private var state: BindOperatorType = .free
    /// 是否忽略相同的值
    private var isIgnoreSameValue: Bool = true
    /// 绑定忽略次数
    private var bindIgnoreTimes: Int = 0
    /// 绑定忽略次数和相同值绑定是否联动
    /// true：相同的值不消耗忽略次数
    /// false：不管值是否相同都消耗忽略次数
    private var bindIgnoreTimesByIgnoreValue: Bool = true
    
    /// 初始化绑定操作者
    /// - Parameters:
    ///   - bindTarget: 绑定者
    ///   - bindKeyPath: 绑定者绑定的属性键值
    init(bindTarget: BinderObject?, bindKeyPath: ReferenceWritableKeyPath<BinderObject, ValueType>?) {
        self.binderObj = bindTarget
        self.binderKeyPath = bindKeyPath
    }
    
    /// 初始化绑定操作者
    /// - Parameters:
    ///   - bindTarget: 绑定者
    ///   - bindKeyPath: 绑定者绑定的属性键值
    init(bindTarget: BinderObject?, binderKeyPathString: String?) {
        self.binderObj = bindTarget
        self.binderKeyPathString = binderKeyPathString
    }
    
    /// 添加被绑定者通知
    /// - Parameters:
    ///   - hijackee: 被绑定者
    ///   - hijackeeKey: 被绑定者的keyPath
    ///
    ///      keyPath取值为 #KeyPath(UITextFild.text)
    func bind(on hijackee: HijackeeObject?, keyPath hijackeeKey: String) {
        observation?.invalidate()
        observation = nil
        if let object = hijackee as? UIView, hijackeeKey == "text" {
            do {
                try handleControl(hijackee as? UIView)
            }catch {
                object.addObserver(self, forKeyPath: hijackeeKey, options: [.old, .new], context: nil)
              }
        }else {
            hijackee?.addObserver(self, forKeyPath: hijackeeKey, options: [.old, .new], context: nil)
        }
        hijackeeObj = hijackee
        hijackeeObjKey = hijackeeKey
        state = .running
    }
    
    /// 添加被绑定者通知
    /// - Parameters:
    ///   - hijackee: 被绑定者
    ///   - hijackeeKeyPath: 被绑定者的键值
    func bind(on hijackee: HijackeeObject?, keyPath hijackeeKeyPath: ReferenceWritableKeyPath<HijackeeObject, ValueType>) {
        if let _ = hijackee as? UIControl, hijackeeKeyPath == \UITextField.text {
            NotificationCenter.default.addObserver(self, selector: #selector(controlValueChanged(_:)), name: UITextField.textDidChangeNotification, object: hijackee)
        }
        else if let _ = hijackee as? UITextView, hijackeeKeyPath == \UITextView.text {
            NotificationCenter.default.addObserver(self, selector: #selector(controlValueChanged(_:)), name: UITextView.textDidChangeNotification, object: hijackee)
        }
        observation?.invalidate()
        observation = nil
        observation = hijackee?.observe(hijackeeKeyPath, options: [.old, .new], changeHandler: {[weak self] _, changed in
            guard let self = self else { return }
            if !self.bindIgnoreTimesByIgnoreValue && self.bindIgnoreTimes > 0 {
                self.bindIgnoreTimes -= 1
                return
            }
            
            if self.state == .running {
                if let newValue = changed.newValue {
                    self.handleBinderValueChange(newValue: newValue)
                }
            }
        })
        state = .running
        hijackeeObj = hijackee
        hijackeeObjKeyPath = hijackeeKeyPath
    }
    
    /// 接收被绑定者属性变化通知
    /// - Parameters:
    ///   - keyPath: 被绑定者的键值
    ///   - object: 被绑定者
    ///   - change: 变化
    ///   - context: 上下文
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        
        if !self.bindIgnoreTimesByIgnoreValue && self.bindIgnoreTimes > 0 {
            self.bindIgnoreTimes -= 1
            return
        }
        
        if self.state == .running {
            if let newValue = change?[.newKey] {
                self.handleBinderValueChange(newValue: newValue)
            }
        }
    }
    
    /// UITextField和UITextView的文本变化回调发方法
    /// - Parameter control: UITextField
    @objc private func controlValueChanged(_ notification: Notification) {
        
        if !self.bindIgnoreTimesByIgnoreValue && self.bindIgnoreTimes > 0 {
            self.bindIgnoreTimes -= 1
            return
        }
        
        if self.state == .running {
            if let control = notification.object as? UITextField {
                self.handleBinderValueChange(newValue: control.text as Any)
            }else if let control = notification.object as? UITextView {
                self.handleBinderValueChange(newValue: control.text as Any)
            }
        }
    }
    
    /// 特殊处理一下UITextField和UITextView
    /// - Parameter control: UITextField或者UITextView
    private func handleControl(_ control: UIView?) throws {
        observation?.invalidate()
        observation = nil
        if let textControl = control as? UITextField {
            /// 这句话是监听UITextField手动输入改变
            NotificationCenter.default.addObserver(self, selector: #selector(controlValueChanged(_:)), name: UITextField.textDidChangeNotification, object: control)
            /// 这句话是监听UITextField直接设置text
            observation = textControl.observe(\UITextField.text, options: [.old, .new], changeHandler: {[weak self] _, changed in
                guard let self = self else { return }
                
                if !self.bindIgnoreTimesByIgnoreValue && self.bindIgnoreTimes > 0 {
                    self.bindIgnoreTimes -= 1
                    return
                }
                
                if self.state == .running {
                    if let newValue = changed.newValue {
                        self.handleBinderValueChange(newValue: newValue as Any)
                    }
                }
            })
        }else if let textControl = control as? UITextView {
            /// 这句话是监听UITextView手动输入改变
            NotificationCenter.default.addObserver(self, selector: #selector(controlValueChanged(_:)), name: UITextView.textDidChangeNotification, object: control)
            /// 这句话是监听UITextView直接设置text
            observation = textControl.observe(\UITextView.text, options: [.old, .new], changeHandler: {[weak self] _, changed in
                guard let self = self else { return }
                
                if !self.bindIgnoreTimesByIgnoreValue && self.bindIgnoreTimes > 0 {
                    self.bindIgnoreTimes -= 1
                    return
                }
                
                if self.state == .running {
                    if let newValue = changed.newValue as? (any Equatable) {
                        self.handleBinderValueChange(newValue: newValue as Any)
                    }
                }
            })
        }else{
            throw BindError.handleError("使用常规属性监听处理")
        }
    }
    
    /// 修改绑定者的值
    /// - Parameter newValue: 改变后的值
    private func handleBinderValueChange(newValue: Any) {
        /// 这里也判断一下
        /// 可能存在正在处理的时候，取消了绑定，极小概率事件
        if self.state == .running, !isSyncing, let newValue = newValue as? ValueType, let _ = binderObj {
            isSyncing = true
            if let newValue = newValue as? (any Equatable), isIgnoreSameValue {
                handleValue(value: newValue)
            }else{
                if let _ = binderKeyPath {
                    /// 忽略次数用完之后才修改值
                    if self.bindIgnoreTimes <= 0 {
                        binderObj![keyPath: binderKeyPath!] = newValue
                    }
                }else if let _ = binderKeyPathString {
                    /// 忽略次数用完之后才修改值
                    if self.bindIgnoreTimes <= 0 {
                        binderObj!.setValue(newValue, forKey: binderKeyPathString!)
                    }
                }
                /// 消耗忽略次数
                if self.bindIgnoreTimesByIgnoreValue && self.bindIgnoreTimes > 0 {
                    self.bindIgnoreTimes -= 1
                }
            }
            isSyncing = false
        }
    }
    
    /// 处理可以相等的值
    /// - Parameter value: 值
    private func handleValue<T>(value: T) where T: Equatable {
        if let _ = binderKeyPath {
            if let oldValue = binderObj![keyPath: binderKeyPath!] as? (any Equatable), oldValue as! T != value {
                /// 忽略次数用完之后才修改值
                if self.bindIgnoreTimes <= 0 {
                    binderObj![keyPath: binderKeyPath!] = value as! ValueType
                }
                if self.bindIgnoreTimesByIgnoreValue && self.bindIgnoreTimes > 0 {
                    self.bindIgnoreTimes -= 1
                }
            }
        }else if let _ = binderKeyPathString {
            if let oldValue = binderObj?.value(forKey: binderKeyPathString!) as? (any Equatable), oldValue as! T != value {
                /// 忽略次数用完之后才修改值
                if self.bindIgnoreTimes <= 0 {
                    binderObj?.setValue(value, forKey: binderKeyPathString!)
                }
                if self.bindIgnoreTimesByIgnoreValue && self.bindIgnoreTimes > 0 {
                    self.bindIgnoreTimes -= 1
                }
            }
        }
    }
    
    deinit {
        free()
    }
}

extension CSBindOperator {
    private enum BindError: Error {
        case handleError(String)
    }
    
    private enum BindOperatorType {
        case running
        case pause
        case free
    }
}

extension CSBindOperator {
    
    func free() {
        state = .free
        observation?.invalidate()
        observation = nil
        if let key = hijackeeObjKey {
            hijackeeObj?.removeObserver(self, forKeyPath: key)
        }
        binderObj = nil
        binderKeyPath = nil
        binderKeyPathString = nil
        NotificationCenter.default.removeObserver(self)
    }
    
    func pause() {
        state = .pause
    }
    
    func run() {
        if state == .pause {
            state = .running
        }
    }
    
    func freshIgnoreSameValue(_ isIgnor: Bool) {
        isIgnoreSameValue = isIgnor
    }
    
    /// 设置忽略次数
    /// - Parameter time: 忽略次数
    func updateIgnoreTimes(_ time: Int) {
        bindIgnoreTimes = time
    }
    
    /// 设置绑定忽略次数和相同值绑定是否联动
    /// - Parameter value: 值
    func updateBindIgnoreTimesByIgnoreValue(_ value: Bool) {
        bindIgnoreTimesByIgnoreValue = value
    }
    
    func immediatelyExcute() {
        if let _ = hijackeeObj {
            var value: Any? = nil
            if let _ = hijackeeObjKeyPath {
                value = hijackeeObj![keyPath: hijackeeObjKeyPath!]
            }else if let _ = hijackeeObjKey {
                value = hijackeeObj!.value(forKey: hijackeeObjKey!)
            }else{
                return
            }
            if self.state == .running, !isSyncing, let newValue = value as? ValueType, let _ = binderObj {
                isSyncing = true
                if let _ = binderKeyPath {
                    binderObj![keyPath: binderKeyPath!] = newValue
                }else if let _ = binderKeyPathString {
                    binderObj!.setValue(newValue, forKey: binderKeyPathString!)
                }
                isSyncing = false
            }
        }
    }
}
